#include <stdlib.h>
#include "add.h"
#include "math.h"
#include "operations.h"
#define DEBUG 0
#define NOT_SET -1
#define CANDIDATE_FAIL -10000

// --- SOLVE  --------------------------------------------------------------------------------------------------------------------

/*
 * @return 1 sse una mossa qualsiasi viene compiuta; 0 altrimenti.
 * Siccome i criteri di aspirazione devono im//printf("non in path ");pedire che una causa di ciò sia la TabooList
 * satura (per quanto questo possa essere improbabile), l'unico motivo che mi viene in mente è che non ci sia niente nell'intorno che rispetti TQOK.
 * NOTA per lo sviluppo di solveRemoveNeighbourhood(): anche quella potrà fallire, se il percorso è tornato un ciclo minimale! */
int solveAddNeighbourhood(Pgraph pg, PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now, pLOSConfiguration pLOSconfig, bool notWorseningMoves){

	/** Cache del migliore ADD che si è trovato finora. best.bestScore è univoco per lo stato di scelta non compiuta. */
	PbestAddRemoveChoice pbest;
	pbest = malloc(sizeof(BestAddRemoveChoice));
	/** Miglior punteggio ottenibile. Se la valutazione è <i>pure</i>, coincide con un profitto, altrimenti con un punteggio
	 * pesato. Nel primo caso, durante l'esplorazione di ADD e anche al suo termine può benissimo raggiungere valori inferiori
	 * al profitto iniziale nell'esplorazione: significa in quel caso che stiamo selezionando mosse peggiorative. */
	pbest->bestScore = NOT_SET;
	/** Determinerà alla fine dell'esplorazione d'intorno se la mossa (con successo) sia stata migliorativa xor peggiorativa.*/
	int initialJk = pvs->jk;
	/** arco 'perno' su cui esplorare doppi e triangoli. e_{ij} \in P_k, in documentazione */
	PedgeSol pes_ij;
	resetMemory(pvs->pvp, DIRECTION_NEXT);

	while ((pes_ij = getNearEdgeInVehiclePath(pvs->pvp, DIRECTION_NEXT))!=NULL){
		analyzeDoubleEdgeAddSubNeighbourhood(pg, pvs, pgm, ptl, now, pbest, pes_ij->id, pLOSconfig->evaluate);
		#if DEBUG > 1
			printf("al termine dei double, best: e1=(%d,%d), e2=(%d,%d) score=%.2f\n", pbest->e_il.i+1, pbest->e_il.j+1, pbest->e_jl.i+1, pbest->e_il.j+1, pbest->bestScore);
		#endif
		analyzeTriangleEdgeAddSubNeighbourhood(pg, pvs, pgm, k, ptl, now, pbest, pes_ij->id, pLOSconfig->evaluate);
		#if DEBUG > 1
			printf("al termine dei triangle, best: e1=(%d,%d), e2=(%d,%d) score=%.2f\n", pbest->e_il.i+1, pbest->e_il.j+1, pbest->e_jl.i+1, pbest->e_il.j+1, pbest->bestScore);
		#endif
	}

	if(pbest->bestScore!=NOT_SET) {
			if(!notWorseningMoves)
				makeAddMove(pg, pvs, pgm, k, ptl, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
			if(pvs->jk >= initialJk) {
				if(notWorseningMoves)
					makeAddMove(pg, pvs, pgm, k, ptl, now, pbest, pLOSconfig->addEdgeTenure, pLOSconfig->removeEdgeTenure);
				if(pvs->jk > initialJk)
					return MOVE_STATUS_IMPROVE;
				else
					return MOVE_STATUS_UNCHANGED;
			}
			else
				return MOVE_STATUS_WORSEN;
		}
	return MOVE_STATUS_FAIL;
}

// --- AGGIORNAMENTI ------------------------------------------------------------------------------------------------------------------

void updateBestAddChoice(PbestAddRemoveChoice pbc, EdgeID e_il, EdgeID e_jl, EdgeID e_ij, Weight score) {

	#if DEBUG > 0 //lasciare 0
		printf("\nupdating add with e_il "); printEdgeID(stdout, &e_il);
		printf("\nupdating add with e_jl "); printEdgeID(stdout, &e_jl);
		printf("\noldScore = %f, newScore = %f", pbc->bestScore, score);
	#endif
	pbc->e_il.i = e_il.i; pbc->e_il.j = e_il.j;
	pbc->e_jl.i = e_jl.i; pbc->e_jl.j = e_jl.j;
	pbc->e_ij.i = e_ij.i; pbc->e_ij.j = e_ij.j;
	pbc->bestScore = score;
}

void makeAddMove(Pgraph pg, PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now, PbestAddRemoveChoice pbest, int addTenure, int removeTenure) {

	#if DEBUG > 0
		printf(" makeAdd: +e_il: "); printEdgeGraph(stdout, getEdgeGraph(pg, &pbest->e_il));
		printf(" makeAdd: +e_jl: "); printEdgeGraph(stdout, getEdgeGraph(pg, &pbest->e_jl));
	#endif

	if(!equals(&pbest->e_il, &pbest->e_jl)) { // siamo in add triangolante: i!=j

		#if DEBUG > 0
			printf(" makeAdd: -e_ij: "); printEdgeGraph(stdout, getEdgeGraph(pg, &pbest->e_ij));
		#endif

		removeEdgeOp(&pbest->e_ij, getEdgeGraph(pg, &pbest->e_ij), pvs, pgm, k, ptl, now, removeTenure);
		addEdgeOp(&pbest->e_il, getEdgeGraph(pg, &pbest->e_il), pvs, pgm, k, ptl, now, addTenure, true);
		addEdgeOp(&pbest->e_jl, getEdgeGraph(pg, &pbest->e_jl), pvs, pgm, k, ptl, now, addTenure, true);
	} else {

		addEdgeOp(&pbest->e_il, getEdgeGraph(pg, &pbest->e_il), pvs, pgm, k, ptl, now, ADD_DOUBLE_ADDTENURE, true);
		addEdgeOp(&pbest->e_jl, getEdgeGraph(pg, &pbest->e_jl), pvs, pgm, k, ptl, now, ADD_DOUBLE_ADDTENURE, true);
	}
}

// --- ARCHI DOPPI --------------------------------------------------------------------------------------------------------------------

void analyzeDoubleEdgeAddSubNeighbourhood(Pgraph pg, PvehicleState pvs, PGMatrix pgm,
									PtabooList ptl, TabooExpire now, PbestAddRemoveChoice pbest, PedgeID e_ij, int evaluateMode) {

	EdgeID e; // nome di documentazione!!
	int l, m; // nome di documentazione!!
	Weight tempScore = NOT_SET;

	e.i = e_ij->i;
	e.j = e_ij->j;

	//printf("\n1possibili archi doppi sul nodo %d:\n", e.i+1);
	for (l=(e.i+1); l<e.j; l++){ // e_{il} con i<l<j : braccio sx croce
		e.j=l;
		tempScore = analyzeDoubleEdgeAddCandidate(pg, e, pvs, pgm, ptl, now, evaluateMode);
		//printf("\ncomparing %f with %f\n", tempScore, pbest->bestScore);
		if(tempScore >= pbest->bestScore)
			updateBestAddChoice(pbest, e, e, e, tempScore);
	}
	for (l=(e.j+1); l<*pg->N; l++){  // e_{il} con l>j : braccio dx croce
		e.j=l;
		tempScore = analyzeDoubleEdgeAddCandidate(pg, e, pvs, pgm, ptl, now, evaluateMode);
		//printf("\ncomparing %f with %f\n", tempScore, pbest->bestScore);
		if(tempScore >= pbest->bestScore)
			updateBestAddChoice(pbest, e, e, e, tempScore);
	}

	e.i = e_ij->i;
	e.j = e_ij->j; // e_{jm}
	//printf("\n2possibili archi doppi sul nodo %d\n", e.j+1);
	for (m=0; m<e.i; m++){  // e_{jm} con i<m<j : braccio tx croce
		e.i=m;
		tempScore = analyzeDoubleEdgeAddCandidate(pg, e, pvs, pgm, ptl, now, evaluateMode);
		//printf("\ncomparing %f with %f\n", tempScore, pbest->bestScore);
		if(tempScore >= pbest->bestScore)
			updateBestAddChoice(pbest, e, e, e, tempScore);
	}
	for (m=(e.i+1); m<e.j; m++){ // e_{jm} con i<j<m : braccio bx croce
		e.i=m;
		tempScore = analyzeDoubleEdgeAddCandidate(pg, e, pvs, pgm, ptl, now, evaluateMode);
		//printf("\ncomparing %f with %f\n", tempScore, pbest->bestScore);
		if(tempScore >= pbest->bestScore)
			updateBestAddChoice(pbest, e, e, e, tempScore);
	}
}

Weight analyzeDoubleEdgeAddCandidate(Pgraph pg, EdgeID e, PvehicleState pvs, PGMatrix pgm, PtabooList ptl, TabooExpire now, int evaluateMode) {

	Weight newScore = CANDIDATE_FAIL;

	if (!isEdgeWalkedInVehiclePath(&e, pvs->pvp)) {
		if (isTQOKDoubleAdd(pg, pvs, pgm, &e)){
			if(!isEdgeTaboo(ptl, &e, now)) {
				newScore = 0;
				#if DEBUG > 1
					printf("\ncandidato doppio "); printEdgeID(stdout, &e);
				#endif
				if(!isEdgeGathered(pgm, &e)) {//TODO no, deve essere adattato su ciò che è raccoglibile davvero!
					newScore += evaluateEdge(pg, &e, pvs->tau, pvs->theta, evaluateMode);
					newScore -= evaluateNonProfitableEdge(pg, &e, pvs->tau, evaluateMode); //perchè torno anche indietro!
					#if DEBUG > 1
						printf("\n\t+ %f\n", evaluateEdge(pg, &e, pvs->tau, pvs->theta, evaluateMode));
						printf("\t- %f", evaluateNonProfitableEdge(pg, &e, pvs->tau, evaluateMode));
					#endif
				}
				#if DEBUG > 1
					printf("\nconsegue score = %f\n", newScore);
				#endif
			}
			#if DEBUG > 2
			else {
				printf("voglio aggiungere (ma e' taboo) il doppio ");
				printEdgeID(stdout, &e); printf("\n");
			}
			#endif
		}
	}
	return newScore;
}

// --- TRIANGOLAZIONI --------------------------------------------------------------------------------------------------------------------

void analyzeTriangleEdgeAddSubNeighbourhood(Pgraph pg, PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now,
											PbestAddRemoveChoice pbest, PedgeID pe_ij, int evaluateMode) {

	EdgeID e_il, e_jl; // nome di documentazione!!
	int l;
	/** unicamente per valori di ritorno. */
	Weight tempScore;
	PedgePair triangles = getEdgeTriangles(*pe_ij, *pg->N);

	for(l=0; l<(*pg->N)-2; l++) {
		e_il = triangles[l].e_il;
		e_jl = triangles[l].e_jl;

		tempScore = analyzeTriangleEdgeAddCandidate(pg, *pe_ij, e_il, e_jl, pvs, pgm, k, ptl, now, evaluateMode);
		//printf("\ncomparing %u with %u\n", tempJk, pbest->bestScore);
		if(tempScore >= pbest->bestScore)
			updateBestAddChoice(pbest, e_il, e_jl, *pe_ij, tempScore);
	}
}

Weight analyzeTriangleEdgeAddCandidate(Pgraph pg, EdgeID e_ij, EdgeID e_il, EdgeID e_jl,
									 PvehicleState pvs, PGMatrix pgm, int k, PtabooList ptl, TabooExpire now, int evaluateMode) {
	Weight newScore = CANDIDATE_FAIL;

	if (isTQOKTriangleAdd(pg, pvs, pgm, &e_ij, &e_il, &e_jl, k)){
		if( !isEdgeTaboo(ptl, &e_il, now) && !isEdgeTaboo(ptl, &e_jl, now) && !isEdgeTaboo(ptl, &e_ij, now) ) {

			newScore = 0; //cioè faccio la mossa non raccogliendo
			if(!isEdgeGathered(pgm, &e_il))
				newScore += evaluateEdge(pg, &e_il, pvs->tau, pvs->theta, evaluateMode);
			if(!isEdgeGathered(pgm, &e_jl))
				newScore += evaluateEdge(pg, &e_jl, pvs->tau, pvs->theta, evaluateMode);
			if( amIGatheringEdge(pgm, &e_ij, k))
				newScore -= evaluateEdge(pg, &e_ij, pvs->tau, pvs->theta, evaluateMode);
			#if DEBUG > 1
				printf("candidato triplo "); printEdgeID(stdout, &e_il); printEdgeID(stdout, &e_jl); printEdgeID(stdout, &e_ij); printf("consegue score = %f\n", newScore);
			#endif
		}
		#if DEBUG > 2
		else {
			printf("voglio aggiungere (ma taboo): %d, %d, %d ",
					isEdgeTaboo(ptl, &e_il, now), isEdgeTaboo(ptl, &e_jl, now), isEdgeTaboo(ptl, &e_ij, now));
			printEdgeID(stdout, &e_il);  printEdgeID(stdout, &e_jl); printEdgeID(stdout, &e_ij); printf("\n");
		}
		#endif
	}
	return newScore;
}

//-------- TQ OK CHECKS ------------------------------------------------------------------------------------------------------------

bool isTQOKDoubleAdd(Pgraph pg, PvehicleState pvs, PGMatrix pgm, PedgeID pe){
	return	((pvs->tau+ 2 * getDeltaTimeAddingEdgeID(pg,pe)
			<=pg->T) && //tempi
			(pvs->theta+ getDeltaLoadAddingEdgeID(pg,pe,pgm)
			<= pg->Q)
			); //capacita
}

bool isTQOKTriangleAdd(Pgraph pg, PvehicleState pvs, PGMatrix pgm, PedgeID pe_ij, PedgeID pe_il, PedgeID pe_jl, int k){

	return	(pvs->tau+
				getDeltaTimeAddingEdgeID(pg,pe_il)+
				getDeltaTimeAddingEdgeID(pg,pe_jl)+
				getDeltaTimeRemovingEdgeID(pg,pe_ij)
			<= pg->T) &&
			(pvs->theta+
				getDeltaLoadAddingEdgeID(pg,pe_il,pgm)+
				getDeltaLoadAddingEdgeID(pg,pe_jl,pgm)+
				getDeltaLoadRemovingEdgeID(pg,pe_ij,k,pgm,pvs)
			<= pg->Q);
}
